import Vue from 'vue'
import { newNode } from './utils'
export default class VirtualTreeStore {
    idKey = null
    childrenKey = null
    itemHeight = null
    
    nodesMap = null

    rootNodesMap = null

    filterMethod = null

    props = Vue.observable({
        nodes: [],
        currentId: null
    })

    constructor (options) {
        options = Object.assign({
            data: [],
            itemHeight: null,
            idKey: null,
            current: null,
            expandLevel: null,
            expandKeys: null,
            childrenKey: null,
            filterMethod: null,
            onCurrent: null
        }, options)
        this.idKey = options.idKey
        this.itemHeight = options.itemHeight
        this.childrenKey = options.childrenKey
        this.filterMethod = options.filterMethod
        this.onCurrent = options.onCurrent
        this.setData(options.data)
        this.expandNodesByLevel(options.expandLevel)
        this.expandNodesByKeys(options.expandKeys)
        this.setCurrentNodeId(options.current)
    }

    getNode (node) {
        let nodeId = node
        if (node && typeof node === 'object') {
            nodeId = node.id
        }
        return this.nodesMap[nodeId]
    }

    insertNode (data, parentNode) {
        data = data || {}
        const node = newNode(data, this.idKey)
        if (!node) {
            return
        }
        if (this.getNode(node)) {
            throw new Error('已存在相同标识的节点')
        }
        parentNode = this.getNode(parentNode)
        if (!parentNode) {
            node.height = this.itemHeight
            node.level = 1
            node.parent = null
            this.rootNodesMap[node.id] = node
            this.props.nodes.push(node)
        } else {
            node.parent = parentNode
            node.level = parentNode.level + 1
            if (parentNode.removed) {
                node.removed = true
            }
            if (!parentNode.height) {
                node.height = 0
            }
            if (parentNode.expanded) {
                node.height = this.itemHeight
            } else {
                node.height = 0
            }
            const parentIndex = this.getNodeIndex(parentNode)
            let insertIndex = -1
            if (!parentNode.children) {
                Vue.set(parentNode, 'children', [])
                insertIndex = parentIndex + 1
            } else {
                insertIndex = parentIndex + this.getChildrensNodes(parentNode).length + 1
            }
            if (insertIndex < 0) {
                throw new Error('插入节点失败')
            }
            parentNode.children.push(node)
            this.props.nodes.splice(insertIndex, 0, node)
        }
        this.nodesMap[node.id] = node
    }

    removeNode (node) {
        node = this.getNode(node)
        if (!node) {
            return
        }
        const index = this.getNodeIndex(node)
        this.getChildrensNodes(node).forEach((cn) => {
            delete this.nodesMap[cn.id]
            this.props.nodes.splice(index + 1, 1)
        })
        if (node.parent) {
            node.parent.children = node.parent.children.filter(n => n.id !== node.id)
        }
        delete this.rootNodesMap[node.id]
        delete this.nodesMap[node.id]
        this.props.nodes.splice(index, 1)
    }

    /**
     * 回收节点
    */
    refuseNode (node) {
        node = this.getNode(node)
        if (!node) {
            return
        }
        if (!node.state) {
            node.state = {
                height: node.height,
                expanded: node.expanded
            }
        }
        node.height = 0
        node.removed = true
    }

    /**
     * 恢复节点
    */
    restoreNode (node) {
        node = this.getNode(node)
        if (!node) {
            return
        }
        if (node.state) {
            node.height = node.state.height
            node.expanded = node.state.expanded
            delete node.state
        }
        node.removed = false
    }

    /**
     * 隐藏节点
    */
    hideNode (node) {
        node = this.getNode(node)
        if (!node || node.removed) {
            return
        }
        node.height = 0
    }

    /**
     * 显示节点
    */
    showNode (node) {
        node = this.getNode(node)
        if (!node || node.removed) {
            return
        }
        if (node.parent) {
            if (node.parent.expanded) {
                node.height = this.itemHeight
            }
        } else {
            node.height = this.itemHeight
        }
    }

    getParentNode (node) {
        node = this.getNode(node)
        if (!node) {
            return
        }
        return node.parent
    }

    getClosestNodes (node) {
        const rs = []
        if (!node) {
            return rs
        }
        const loop = (node) => {
            if (!node) {
                return
            }
            rs.push(node)
            loop(node.parent)
        }
        loop(node)
        return rs
    }

    getChildrensNodes (node) {
        let rs = []
        if (!node || !node.children) {
            return rs
        }
        const loop = (node) => {
            if (!node.children) {
                return
            }
            node.children.forEach(cn => {
                rs.push(cn)
                loop(cn)
            })
        }
        loop(node)
        return rs
    }

    expandNode (node) {
        node = this.getNode(node)
        if (!node || node.removed) {
            return
        }
        node.expanded = true
        this.showNode(node)
        this.loopChildrensShow(node)
        this.loopParentsExpand(node)
    }

    collapseNode (node) {
        node = this.getNode(node)
        if (!node || node.removed) {
            return
        }
        node.expanded = false
        this.loopChildrensHide(node)
    }

    loopParentsExpand (node) {
        const closestNodes = this.getClosestNodes(node).slice(1)
        if (!closestNodes || !closestNodes.length) {
            return
        }
        closestNodes.forEach(node => {
            if (node.removed) {
                return
            }
            node.expanded = true
            node.height = this.itemHeight
            if (node.children) {
                node.children.forEach(cn => {
                    if (cn.removed) {
                        return
                    }
                    this.showNode(cn)
                })
            }
        })
    }

    /**
     * 设置所有后端节点显示
    */
   loopChildrensShow (node) {
        if (!node || !node.children || !node.children.length) {
            return
        }
        const loop = (nodes, parent) => {
            if (!nodes || !nodes.length) {
                return
            }
            nodes.forEach(node => {
                if (node.removed) {
                    return
                }
                if (parent) {
                    if (parent.expanded) {
                        this.showNode(node)
                    } else {
                        this.hideNode(node)
                    }
                } else {
                    this.showNode(node)
                }
                loop(node.children, node)
            })
        }
        loop(node.children, node)
   }
    
    /**
     * 设置所有后代节点隐藏
    */
    loopChildrensHide (node) {
        if (!node || !node.children || !node.children.length) {
            return
        }
        const loop = (nodes) => {
            if (node.removed) {
                return
            }
            if (!nodes || !nodes.length) {
                return
            }
            nodes.forEach(node => {
                this.hideNode(node)
                loop(node.children)
            })
        }
        loop(node.children, node)
    }

    /**
     * 展开到指定层级
    */
    expandNodesByLevel (expandLevel) {
        if (!expandLevel) {
            return
        }
        if (!this.props.nodes) {
            return
        }
        this.props.nodes.forEach(node => {
            if (node.removed) {
                return
            }
            if (node.level <= expandLevel) {
                node.expanded = true
                this.showNode(node)
            } else {
                node.expanded = false
                if (node.level === expandLevel + 1) {
                    this.showNode(node)
                } else {
                    this.hideNode(node)
                }
            }
        })
    }

    expandNodesByKeys (keys) {
        if (!keys || !keys.length) {
            return
        }
        const allExpandNodeMap = keys.reduce((rs, k) => {
            if (!this.nodesMap[k]) {
                return rs
            }
            const nodes = this.getClosestNodes(this.nodesMap[k])
            nodes.forEach(node => {
                rs[node.id] = node
            })
            return rs
        }, {})
        const allShowNodeMap = Object.assign(keys.reduce((rs, k) => {
            if (!this.nodesMap[k]) {
                return rs
            }
            const nodes = this.getClosestNodes(this.nodesMap[k]).slice(1)
            nodes.forEach(node => {
                if (node && node.children) {
                    node.children.forEach(n => {
                        rs[n.id] = true
                    })
                }
            })
            return rs
        }, {}), this.rootNodesMap)
        this.props.nodes.forEach(node => {
            if (node.removed) {
                return
            }
            if (allExpandNodeMap[node.id]) {
                node.expanded = true
                this.showNode(node)
            } else {
                node.expanded = false
                if (allShowNodeMap[node.id]) {
                    this.showNode(node)
                } else {
                    this.hideNode(node)
                }
            }
        })
    }

    setCurrentNodeId (nodeId) {
        const node = this.getNode(nodeId)
        if (!node) {
            return
        }
        this.props.currentId = node.id
        if (this.onCurrent) {
            this.onCurrent({
                node
            })
        }
    }

    getNodeIndex (node) {
        let index = -1
        let nodeId = node
        if (node && typeof node === 'object') {
            nodeId = node.id
        }
        if (!this.nodesMap[nodeId]) {
            return index
        }
        this.props.nodes.some((v, i) => {
            if (v.id === nodeId) {
                index = i
                return true
            }
        })
        return index
    }

    setData (data) {
        const result = []
        const nodesMap = {}
        const rootNodesMap = {}
        if (data && data.length) {
            const loop = (data, level, parent) => {
                if (!data || !data.length) {
                    return
                }
                return data.map(v => {
                    const node = {
                        id: v[this.idKey],
                        level,
                        height: this.itemHeight,
                        expanded: true,
                        removed: false,
                        parent,
                        data: v
                    }
                    if (level === 1) {
                        rootNodesMap[v[this.idKey]] = node
                    }
                    result.push(node)
                    nodesMap[node[this.idKey]] = node
                    if (v[this.childrenKey]) {
                        node.children = loop(v[this.childrenKey], level + 1, node)
                    }
                    return node
                })
            }
            loop(data, 1, null)
        }
        this.nodesMap = nodesMap
        this.rootNodesMap = rootNodesMap
        this.props.nodes = result
    }

    filter (keyword) {
        keyword = typeof keyword === 'string' ? keyword.trim() : String(keyword)
        if (!this.filterMethod) {
            return
        }
        if (!keyword) {
            this.props.nodes.filter(node => {
                this.restoreNode(node)
            })
        } else {
            const currentData = this.props.nodes.filter(node => {
                const rs = this.filterMethod({
                    node,
                    keyword
                })
                if (!rs) {
                    this.refuseNode(node)
                }
                return rs
            })
            currentData.forEach(node => {
                const closestNodes = this.getClosestNodes(node)
                closestNodes.forEach(n => {
                    this.restoreNode(n)
                    this.showNode(n)
                })
                this.expandNode(node)
            })
        }
    }
}